/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2020 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockFocusController.h"
#include "QxDockAreaTitleBar.h"
#include "QxDockAreaWidget.h"
#include "QxDockContainerWidget.h"
#include "QxDockManager.h"
#include "QxDockWidget.h"
#include "QxDockWidgetTab.h"
#include "QxDockFloatingContainer.h"
#include "QxDockAutoHideTab.h"

#include <QAbstractButton>
#include <QApplication>
#include <QPointer>
#include <QWindow>

#include <algorithm>
#include <iostream>

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#include "linux/QxDockFloatingWidgetTitleBar.h"
#endif

QX_BEGIN_NAMESPACE

static const char *const FocusedDockWidgetProperty = "FocusedDockWidget";

/**
 * Private data class of DockFocusController class (pimpl)
 */
struct DockFocusControllerPrivate {
    DockFocusController *_this;
    QPointer<DockWidget> FocusedDockWidget = nullptr;
    QPointer<DockAreaWidget> FocusedArea = nullptr;
    QPointer<DockWidget> OldFocusedDockWidget = nullptr;
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    QPointer<DockFloatingContainer> FloatingWidget = nullptr;
#endif
    DockManager *dockManager;
    bool ForceFocusChangedSignal = false;

    /**
     * Private data constructor
     */
    DockFocusControllerPrivate(DockFocusController *_public);

    /**
     * This function updates the focus style of the given dock widget and
     * the dock area that it belongs to
     */
    void updateDockWidgetFocus(DockWidget *dockWidget);
};   // struct DockFocusControllerPrivate

static void updateDockWidgetFocusStyle(DockWidget *dockWidget, bool Focused)
{
    dockWidget->setProperty("focused", Focused);
    dockWidget->tabWidget()->setProperty("focused", Focused);
    dockWidget->tabWidget()->updateStyle();
    internal::repolishStyle(dockWidget);
}

static void updateDockAreaFocusStyle(DockAreaWidget *dockArea, bool Focused)
{
    dockArea->setProperty("focused", Focused);
    internal::repolishStyle(dockArea);
    internal::repolishStyle(dockArea->titleBar());
}

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
static void updateFloatingWidgetFocusStyle(DockFloatingContainer *FloatingWidget, bool Focused)
{
    if (FloatingWidget->hasNativeTitleBar()) {
        return;
    }
    auto TitleBar = qobject_cast<DockFloatingWidgetTitleBar *>(FloatingWidget->titleBarWidget());
    if (!TitleBar) {
        return;
    }
    TitleBar->setProperty("focused", Focused);
    TitleBar->updateStyle();
}
#endif

DockFocusControllerPrivate::DockFocusControllerPrivate(DockFocusController *_public) : _this(_public)
{
}

void DockFocusControllerPrivate::updateDockWidgetFocus(DockWidget *dockWidget)
{
    if (!dockWidget->features().testFlag(DockWidget::DockWidgetFocusable)) {
        return;
    }

    QWindow *Window = nullptr;
    auto DockContainer = dockWidget->dockContainer();
    if (DockContainer) {
        Window = DockContainer->window()->windowHandle();
    }

    if (Window) {
        Window->setProperty(FocusedDockWidgetProperty, QVariant::fromValue(QPointer<DockWidget>(dockWidget)));
    }
    DockAreaWidget *NewFocusedDockArea = nullptr;
    if (FocusedDockWidget) {
        updateDockWidgetFocusStyle(FocusedDockWidget, false);
    }

    DockWidget *old = FocusedDockWidget;
    FocusedDockWidget = dockWidget;
    updateDockWidgetFocusStyle(FocusedDockWidget, true);
    NewFocusedDockArea = FocusedDockWidget->dockAreaWidget();
    if (NewFocusedDockArea && (FocusedArea != NewFocusedDockArea)) {
        if (FocusedArea) {
            QObject::disconnect(FocusedArea, SIGNAL(viewToggled(bool)), _this,
                                SLOT(onFocusedDockAreaViewToggled(bool)));
            updateDockAreaFocusStyle(FocusedArea, false);
        }

        FocusedArea = NewFocusedDockArea;
        updateDockAreaFocusStyle(FocusedArea, true);
        QObject::connect(FocusedArea, SIGNAL(viewToggled(bool)), _this, SLOT(onFocusedDockAreaViewToggled(bool)));
    }

    DockFloatingContainer *NewFloatingWidget = nullptr;
    DockContainer = FocusedDockWidget->dockContainer();
    if (DockContainer) {
        NewFloatingWidget = DockContainer->floatingWidget();
    }

    if (NewFloatingWidget) {
        NewFloatingWidget->setProperty(FocusedDockWidgetProperty,
                                       QVariant::fromValue(QPointer<DockWidget>(dockWidget)));
    }

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    // This code is required for styling the floating widget titlebar for linux
    // depending on the current focus state
    if (FloatingWidget != NewFloatingWidget) {
        if (FloatingWidget) {
            updateFloatingWidgetFocusStyle(FloatingWidget, false);
        }
        FloatingWidget = NewFloatingWidget;

        if (FloatingWidget) {
            updateFloatingWidgetFocusStyle(FloatingWidget, true);
        }
    }
#endif

    if (old == dockWidget && !ForceFocusChangedSignal) {
        return;
    }

    ForceFocusChangedSignal = false;
    if (dockWidget->isVisible()) {
        Q_EMIT dockManager->focusedDockWidgetChanged(old, dockWidget);
    } else {
        OldFocusedDockWidget = old;
        QObject::connect(dockWidget, SIGNAL(visibilityChanged(bool)), _this, SLOT(onDockWidgetVisibilityChanged(bool)));
    }
}

void DockFocusController::onDockWidgetVisibilityChanged(bool Visible)
{
    auto Sender = sender();
    auto dockWidget = qobject_cast<Qx::DockWidget *>(Sender);
    disconnect(Sender, SIGNAL(visibilityChanged(bool)), this, SLOT(onDockWidgetVisibilityChanged(bool)));
    if (dockWidget && Visible) {
        Q_EMIT d->dockManager->focusedDockWidgetChanged(d->OldFocusedDockWidget, dockWidget);
    }
}

DockFocusController::DockFocusController(DockManager *dockManager)
    : Super(dockManager), d(new DockFocusControllerPrivate(this))
{
    d->dockManager = dockManager;
    connect(QApplication::instance(), SIGNAL(focusChanged(QWidget *, QWidget *)), this,
            SLOT(onApplicationFocusChanged(QWidget *, QWidget *)));
    connect(QApplication::instance(), SIGNAL(focusWindowChanged(QWindow *)), this,
            SLOT(onFocusWindowChanged(QWindow *)));
    connect(d->dockManager, SIGNAL(stateRestored()), SLOT(onStateRestored()));
}

DockFocusController::~DockFocusController()
{
    delete d;
}

void DockFocusController::onFocusWindowChanged(QWindow *focusWindow)
{
    if (!focusWindow) {
        return;
    }

    auto vDockWidget = focusWindow->property(FocusedDockWidgetProperty);
    if (!vDockWidget.isValid()) {
        return;
    }

    auto dockWidget = vDockWidget.value<QPointer<DockWidget>>();
    if (!dockWidget) {
        return;
    }

    d->updateDockWidgetFocus(dockWidget);
}

void DockFocusController::onApplicationFocusChanged(QWidget *focusedOld, QWidget *focusedNow)
{
    Q_UNUSED(focusedOld);

    if (d->dockManager->isRestoringState()) {
        return;
    }

    QX_DOCK_PRINT("DockFocusController::onApplicationFocusChanged "
              << " old: " << focusedOld << " new: " << focusedNow);
    if (!focusedNow) {
        return;
    }

    DockWidget *dockWidget = qobject_cast<DockWidget *>(focusedNow);
    if (!dockWidget) {
        dockWidget = internal::findParent<DockWidget *>(focusedNow);
    }

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    if (!dockWidget) {
        return;
    }
#else
    if (!dockWidget || dockWidget->tabWidget()->isHidden()) {
        return;
    }
#endif

    d->updateDockWidgetFocus(dockWidget);
}

void DockFocusController::setDockWidgetTabFocused(DockWidgetTab *Tab)
{
    auto dockWidget = Tab->dockWidget();
    if (dockWidget) {
        d->updateDockWidgetFocus(dockWidget);
    }
}

void DockFocusController::clearDockWidgetFocus(DockWidget *dockWidget)
{
    dockWidget->clearFocus();
    updateDockWidgetFocusStyle(dockWidget, false);
}

void DockFocusController::setDockWidgetFocused(DockWidget *focusedNow)
{
    d->updateDockWidgetFocus(focusedNow);
}

void DockFocusController::onFocusedDockAreaViewToggled(bool Open)
{
    if (d->dockManager->isRestoringState()) {
        return;
    }

    DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(sender());
    if (!dockArea || Open) {
        return;
    }
    auto Container = dockArea->dockContainer();
    auto OpenedDockAreas = Container->openedDockAreas();
    if (OpenedDockAreas.isEmpty()) {
        return;
    }

    d->updateDockWidgetFocus(OpenedDockAreas[0]->currentDockWidget());
}

void DockFocusController::notifyWidgetOrAreaRelocation(QWidget *DroppedWidget)
{
    if (d->dockManager->isRestoringState()) {
        return;
    }

    DockWidget *dockWidget = qobject_cast<DockWidget *>(DroppedWidget);
    if (!dockWidget) {
        DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(DroppedWidget);
        if (dockArea) {
            dockWidget = dockArea->currentDockWidget();
        }
    }

    if (!dockWidget) {
        return;
    }

    d->ForceFocusChangedSignal = true;
    DockManager::setWidgetFocus(dockWidget);
}

void DockFocusController::notifyFloatingWidgetDrop(DockFloatingContainer *FloatingWidget)
{
    if (!FloatingWidget || d->dockManager->isRestoringState()) {
        return;
    }

    auto vDockWidget = FloatingWidget->property(FocusedDockWidgetProperty);
    if (!vDockWidget.isValid()) {
        return;
    }

    auto dockWidget = vDockWidget.value<QPointer<DockWidget>>();
    if (dockWidget) {
        dockWidget->dockAreaWidget()->setCurrentDockWidget(dockWidget);
        DockManager::setWidgetFocus(dockWidget);
    }
}

//==========================================================================
void DockFocusController::onStateRestored()
{
    if (d->FocusedDockWidget) {
        updateDockWidgetFocusStyle(d->FocusedDockWidget, false);
    }
}

//==========================================================================
DockWidget *DockFocusController::focusedDockWidget() const
{
    return d->FocusedDockWidget.data();
}

QX_END_NAMESPACE
